﻿using System;
using System.Collections.Generic;
using System.Text;
using Vbyte.TextReader.Util;
using System.IO;
using System.Text.RegularExpressions;

namespace Vbyte.TextReader
{
    /// <summary>
    /// 日志阅读抽象类
    /// </summary>
    /// <typeparam name="T">实例类型</typeparam>
    public abstract class LogReader<T> : Object, IDisposable
        where T : ILogItemTime
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LogReader"/> class.
        /// </summary>
        /// <param name="logDir">日志存放目录</param>
        /// <param name="filenamePattern">文件名正则匹配模式</param>
        /// <param name="whichDay">日志阅读的日期</param>
        public LogReader(string logDir, string[] filenamePattern, DateTime whichDay)
        {
            LogDirectory = logDir;
            Date = whichDay;
            FileNamePatterns = filenamePattern;

            PrepareFileRead();
        }

        /// <summary>
        /// 日志存放目录
        /// </summary>
        public string LogDirectory { get; set; }

        /// <summary>
        /// 日志阅读的日期
        /// </summary>
        public DateTime Date { get; set; }

        /// <summary>
        /// 阅读器名称
        /// </summary>
        public string Name { get; set; }

        private string[] _fnPatterns = new string[0];
        /// <summary>
        /// 文件名正则匹配模式(必须包含分组Date)
        /// </summary>
        public string[] FileNamePatterns
        {
            get { return _fnPatterns; }
            set
            {
                if (value == null || value.Length < 1)
                {
                    throw new InvalidOperationException("必须设置日志文件名匹配规则！");
                }

                //API-(?<Date>\d{4}\-\d{2}\-\d{2})
                for (int i = 0, j = value.Length; i < j; i++)
                {
                    if (!value[i].Contains("?<Date>"))
                    {
                        throw new InvalidOperationException("匹配规则不包含匹配分组Date!");
                    }
                    else
                    {
                        string dPattern = String.Concat("(", Date.ToString("yyyy-MM-dd").Replace("-", "\\-"), ")");
                        value[i] = value[i].Replace(StringHelper.GetSnippetBlock(ref value[i], "?<Date>", "(", ")"), dPattern);
                    }
                }
                _fnPatterns = value;
            }
        }

        /// <summary>
        /// 文本阅读器
        /// </summary>
        protected TxtFileReader Reader { get; set; }

        /// <summary>
        /// 当前文件阅读索引位置
        /// </summary>
        protected int CurrentFileIndex = -1;

        /// <summary>
        /// 所有待读的文件路径数组
        /// </summary>
        protected string[] ReadFilePaths = new string[0];

        private void PrepareFileRead()
        {
            List<string> logList = new List<string>();
            SetLogFileList(LogDirectory, ref logList);
            ReadFilePaths = logList.ToArray();
        }

        private void SetLogFileList(string dir, ref List<string> logFiles)
        {
            DirectoryInfo LogDi = new DirectoryInfo(dir);
            if (LogDi.Exists)
            {
                foreach (FileInfo fi in LogDi.GetFiles())
                {
                    foreach (string pattern in FileNamePatterns)
                    {
                        if (Regex.IsMatch(fi.Name, pattern, RegexOptions.IgnoreCase))
                        {
                            logFiles.Add(fi.FullName);
                        }
                    }
                }

                foreach (DirectoryInfo di in LogDi.GetDirectories())
                {
                    SetLogFileList(di.FullName, ref logFiles);
                }
            }
        }


        /// <summary>
        /// 前进阅读，表示当前日志是否可读。
        /// </summary>
        /// <returns>如果返回为true，则还有数据需要阅读。</returns>
        public abstract bool Read();

        /// <summary>
        /// 判断是否可读
        /// </summary>
        /// <returns>如果返回为true，则还有数据需要阅读。</returns>
        public bool CanRead()
        {
            bool result = true;

            bool blnSetReader = true, blnNextLogFile = false;
            if (Reader != null)
            {
                if (Reader.EndOfStream)
                {
                    Reader.Dispose();

                    blnNextLogFile = true;
                    blnSetReader = true;
                }
                else
                {
                    blnSetReader = false;
                }
            }

            if (blnSetReader && ReadFilePaths.Length > 0)
            {
                CurrentFileIndex = CurrentFileIndex + 1;
                if (blnNextLogFile && CurrentFileIndex + 1 < ReadFilePaths.Length) CurrentFileIndex++;

                if (CurrentFileIndex < ReadFilePaths.Length)
                {
                    Reader = new TxtFileReader(ReadFilePaths[CurrentFileIndex]);

                    Console.WriteLine("符合条件的日志文件路径：");
                    Console.WriteLine(string.Join("\n", ReadFilePaths));
                    Console.WriteLine("当前阅读第{0}个文件", CurrentFileIndex + 1);
                }
                else
                {
                    result = false;
                }
            }
            else
            {
                if (ReadFilePaths.Length == 0)
                {
                    //没有相关日志文件
                    result = false;
                }
            }

            return result;
        }

        /// <summary>
        /// 阅读器当前实例
        /// </summary>
        /// <returns>返回阅读器当前所在的实例</returns>
        public abstract T GetCurrent();

        /// <summary>
        /// 设置阅读器的游标位置
        /// </summary>
        /// <param name="fileIndex">文件索引</param>
        /// <param name="filePos">文件内位置</param>
        public void SetCursor(int fileIndex, long filePos)
        {
            if (fileIndex > -1 && fileIndex < ReadFilePaths.Length)
            {
                if (CurrentFileIndex != fileIndex)
                {
                    CurrentFileIndex = fileIndex;
                    if (Reader != null) Reader.Dispose();

                    Reader = new TxtFileReader(ReadFilePaths[CurrentFileIndex]);
                    Reader.GotoPostion(filePos);
                }
            }
        }

        /// <summary>
        /// 获取所有满足条件的日志集合
        /// </summary>
        /// <returns>日志实例集合</returns>
        public T[] GetAll()
        {
            //SortedList<DateTime, T> sList = new SortedList<DateTime, T>();
            List<T> sList = new List<T>();
            while (Read())
            {
                T instance = GetCurrent();
                //sList.Add(instance.LogTime, instance);
                sList.Add(instance);
            }

            //T[] r = new T[sList.Count];
            //sList.Values.CopyTo(r, 0);
            //return r;

            return sList.ToArray();
        }

        /// <summary>
        /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
        /// </summary>
        public void Dispose()
        {
            if (Reader != null)
            {
                Reader.Dispose();
            }
        }

    }


    /// <summary>
    /// 文本文件阅读器
    /// </summary>
    public class TxtFileReader : IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LogFileReader"/> class.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        public TxtFileReader(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new InvalidOperationException("日志文件不存在！");
            }
            else
            {
                fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                SetFileEncoding();
            }
        }

        private FileStream fs = null;


        private long _lastLinePos = -1;

        private long _currentPos = -1;

        /// <summary>
        /// 获取阅读器所在的索引位置
        /// </summary>
        /// <returns>索引位置</returns>
        public long GetPosition()
        {
            return _currentPos;
        }

        /// <summary>
        /// 是否在流结尾
        /// </summary>
        /// <value><c>true</c> if [end of stream]; otherwise, <c>false</c>.</value>
        public bool EndOfStream
        {
            get
            {
                if (fs != null) return !(_currentPos < fs.Length);
                return true;
            }
        }

        /// <summary>
        /// 日志文件的内容编码
        /// </summary>
        internal Encoding ContentEncoding;

        public void SetFileEncoding()
        {
            if (ContentEncoding == null)
            {
                byte[] byteBuffer = new byte[5];
                fs.Read(byteBuffer, 0, byteBuffer.Length);
                fs.Seek(0, SeekOrigin.Begin);

                int byteLen = byteBuffer.Length;
                bool flag = false;
                if ((byteBuffer[0] == 0xfe) && (byteBuffer[1] == 0xff))
                {
                    ContentEncoding = new UnicodeEncoding(true, true);
                    flag = true;
                }
                else if ((byteBuffer[0] == 0xff) && (byteBuffer[1] == 0xfe))
                {
                    if (((byteLen >= 4) && (byteBuffer[2] == 0)) && (byteBuffer[3] == 0))
                    {
                        ContentEncoding = new UTF32Encoding(false, true);
                    }
                    else
                    {
                        ContentEncoding = new UnicodeEncoding(false, true);
                    }
                    flag = true;
                }
                else if (((byteLen >= 3) && (byteBuffer[0] == 0xef)) && ((byteBuffer[1] == 0xbb) && (byteBuffer[2] == 0xbf)))
                {
                    ContentEncoding = Encoding.UTF8;
                    flag = true;
                }
                else if (((byteLen >= 3) && (byteBuffer[0] == 0x2b)) && ((byteBuffer[1] == 0x2f) && (byteBuffer[2] == 0x76)))
                {
                    ContentEncoding = Encoding.UTF7;
                    flag = true;
                }
                else if ((((byteLen >= 4) && (byteBuffer[0] == 0)) && ((byteBuffer[1] == 0) && (byteBuffer[2] == 0xfe))) && (byteBuffer[3] == 0xff))
                {
                    ContentEncoding = new UTF32Encoding(true, true);
                    flag = true;
                }

                if (!flag)
                {
                    //if (Encoding.Default.GetMaxByteCount((int)fs.Length) < fs.Length)
                    //{
                    //    ContentEncoding = Encoding.UTF8;
                    //}
                    //else
                    //{
                    ContentEncoding = Encoding.Default;
                    //}
                }

            }

        }


        /// <summary>
        /// 后退到上一行
        /// </summary>
        public void GotoLastLine()
        {
            if (_lastLinePos != -1)
            {
                GotoPostion(_lastLinePos);
            }
        }

        /// <summary>
        /// 后退到符合条件的行，如果没有找到符合条件的则转到第1行。
        /// </summary>
        /// <param name="m">对当前行字符的判断</param>
        public void GotoLastMatchLine(Predicate<string> m)
        {
            ReadLineBack();//当前行
            string backLine = ReadLineBack(); //上一行

            //int safe = 0;

            while (backLine != null && !m(backLine))
            {
                ReadLineBack();//当前行
                backLine = ReadLineBack();//上一行

                //Console.WriteLine(backLine);
                //safe++;
                //if (safe > 5) break;
            }
            ReadLineBack();
        }

        /// <summary>
        /// 转到指定位置
        /// </summary>
        /// <param name="pos">位置索引</param>
        public void GotoPostion(long pos)
        {
            if (fs != null && (pos > 0 && pos < fs.Length))
            {
                fs.Position = pos;
                _currentPos = pos;
            }
        }

        /// <summary>
        /// 向后读取一行
        /// </summary>
        /// <returns>如果存在数据则返回所在行数据，并前进到下一行，否则为null。</returns>
        public string ReadLineBack()
        {
            //在开始位置
            if (fs.Position == 0) { _currentPos = 0;  return null;  }

            bool blnDoRead = true;
            byte[] chrBuffer = new byte[1];

            if (fs.Position > 0) fs.Position = fs.Position - 1;
            //Console.WriteLine();
            //Console.WriteLine("*起始位置：{0}", fs.Position);

         ReadChar:

            //设置读以前的索引位置
            _lastLinePos = fs.Position;

            while (fs.Read(chrBuffer, 0, 1) != -1 && blnDoRead)
            {
                //Console.WriteLine("(w)当前位置：{0}", fs.Position);
                //Console.WriteLine("POS:{0}, Byte:{1}", fs.Position, chrBuffer[0]);
                if (chrBuffer[0] == 0x0A || fs.Position == 1)
                {
                    if (fs.Position == 1) fs.Position = 0;
                    blnDoRead = false;
                }
                else
                {
                    if (fs.Position >= 2) fs.Position = fs.Position - 2;
                }
            }

            //Console.WriteLine("(F)POS:{0}", fs.Position);

            long offSet = _lastLinePos - fs.Position + 1;
            //Console.WriteLine("@ReadLineBack Offset:{0}, Current Idx:{1}", offSet, fs.Position);
            if (fs.Position > 1 && offSet < 2)
            {
                if (fs.Position > 3) fs.Position = fs.Position - 3;
                blnDoRead = true;
                goto ReadChar;
            }

            //设置读之后的位置
            _currentPos = fs.Position;
            //Console.WriteLine("LastPos:{1}, CurPOS:{0}", fs.Position, _lastLinePos);   

            long total = _lastLinePos - _currentPos;
            if (total > 0)
            {
                //Console.WriteLine("BefPOS:{0}", fs.Position);
                if (fs.Position == 1) fs.Position = 0;
                
                byte[] lineBytes = new byte[total];
                fs.Read(lineBytes, 0, (int)total);

                //Console.WriteLine("RetPOS:{0}", fs.Position);
                //设置位置为开始处
                fs.Position = _currentPos;
                return ContentEncoding.GetString(lineBytes).Trim('\r', '\n');

                //string ret = ContentEncoding.GetString(lineBytes).Trim('\r', '\n');
                //Console.WriteLine("(F):{1} ==> {0}", ret, fs.Position);
                //return ret; 
            }

            return null;
        }

        /// <summary>
        /// 读取一行字符串
        /// </summary>
        /// <returns>如果存在数据则返回所在行数据，并前进到下一行，否则为null。</returns>
        public string ReadLine()
        {

            bool blnDoRead = true;
            byte[] chrBuffer = new byte[1];

        ReadChar:
            _lastLinePos = fs.Position;

            while (fs.Read(chrBuffer, 0, 1) != -1 && blnDoRead)
            {
                //Console.WriteLine("POS:{0}, Byte:{1}", fs.Position, chrBuffer[0]);
                if (chrBuffer[0] == 0x0A || fs.Position == fs.Length - 1) blnDoRead = false;
            }

            long offSet = fs.Position - _lastLinePos;
            //Console.WriteLine("ReadLine Offset:{0}", offSet);
            if (fs.Position < fs.Length && offSet <= 2)
            {
                blnDoRead = true;
                goto ReadChar;
            }

            //fs.ReadByte();
            _currentPos = fs.Position;

            long total = _currentPos - _lastLinePos - 1;
            if (total > 0)
            {
                byte[] lineBytes = new byte[total];
                fs.Position = _lastLinePos;
                fs.Read(lineBytes, 0, (int)total);

                return ContentEncoding.GetString(lineBytes).Trim('\r', '\n');
            }

            return null;
        }

        #region IDisposable 成员

        /// <summary>
        /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
        /// </summary>
        public void Dispose()
        {
            if (fs != null)
            {
                fs.Close();
                fs.Dispose();
            }
        }

        #endregion
    }

}
